/*
 *  addr_server.c -- socket-based ip address server.
 *                   8-92 : Dwayne Fontenot : original coding
 */

#include "std.h"
#include "addr_server.h"
#include "socket_ctrl.h"
#include "file_incl.h"
#include "port.h"

#ifdef DEBUG_MACRO
int debug_level = DBG_addr_server;
#endif				/* DEBUG_MACRO */

#define DBG(x) debug(addr_server, x)

/*
 * private local variables.
 */
static connection all_conns[MAX_CONNS];
static int total_conns = 0;
static queue_element_ptr queue_head = NULL;
static queue_element_ptr queue_tail = NULL;
static queue_element_ptr stack_head = NULL;
static int queue_length = 0;
static int conn_fd;

fd_set readmask;

int name_by_ip(int, char *);
int ip_by_name(int, char *);
INLINE_STATIC void process_queue(void);
void init_conns(void);
void init_conn_sock(int, char *);

#ifdef SIGNAL_FUNC_TAKES_INT
void sigpipe_handler(int);
#else
void sigpipe_handler(void);
#endif

INLINE void aserv_process_io(int);
void enqueue_datapending(int, int);
void handle_top_event(void);
void dequeue_top_event(void);
void pop_queue_element(queue_element_ptr *);
void push_queue_element(queue_element_ptr);
void new_conn_handler(void);
void conn_data_handler(int);
int index_by_fd(int);
void terminate(int);

void debug_perror(char *, char *);

void debug_perror(char * what, char * file)
{
	if (file)
		fprintf(stderr, "System Error: %s:%s:%s\n", what, file,
		        port_strerror(errno));
	else
		fprintf(stderr, "System Error: %s:%s\n", what, port_strerror(errno));
}

void init_conns()
{
	int i;

	for (i = 0; i < MAX_CONNS; i++)
	{
		all_conns[i].fd = -1;
		all_conns[i].state = CONN_CLOSED;
		all_conns[i].sname[0] = '\0';

		/* ensure 'leftover' buffer is _always_ null terminated */
		all_conns[i].buf[0] = '\0';
		all_conns[i].buf[IN_BUF_SIZE - 1] = '\0';
		all_conns[i].leftover = 0;
	}
}

/*
 * Initialize connection socket.
 */
void init_conn_sock(int port_num, char * ipaddress)
{
	struct sockaddr_in sin;
	int sin_len;
	int optval;
#ifdef WINSOCK
	WSADATA WSAData;

	WSAStartup(MAKEWORD(1,1), &WSAData);
	atexit(cleanup_sockets);
#endif

	/*
	 * create socket of proper type.
	 */
	if ((conn_fd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
		socket_perror("init_conn_sock: socket", 0);
		exit(1);
	}
	/*
	 * enable local address reuse.
	 */
	optval = 1;
	if (setsockopt(conn_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optval,
	        sizeof(optval)) == -1)
	{
		socket_perror("init_conn_sock: setsockopt", 0);
		exit(2);
	}
	/*
	 * fill in socket address information.
	 */
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = (ipaddress ? inet_addr(ipaddress) : INADDR_ANY);
	sin.sin_port = htons((u_short) port_num);
	/*
	 * bind name to socket.
	 */
	if (bind(conn_fd, (struct sockaddr *) &sin, sizeof(sin)) == -1)
	{
		socket_perror("init_conn_sock: bind", 0);
		exit(3);
	}
	/*
	 * get socket name.
	 */
	sin_len = sizeof(sin);
	if (getsockname(conn_fd, (struct sockaddr *) &sin, &sin_len) == -1)
	{
		socket_perror("init_conn_sock: getsockname", 0);
		exit(4);
	}
	/*
	 * register signal handler for SIGPIPE.
	 */
#if !defined(LATTICE) && defined(SIGPIPE) /* windows has no SIGPIPE */
	if (signal(SIGPIPE, sigpipe_handler) == SIGNAL_ERROR)
	{
		socket_perror("init_conn_sock: signal SIGPIPE", 0);
		exit(5);
	}
#endif
	/*
	 * set socket non-blocking
	 */
	if (set_socket_nonblocking(conn_fd, 1) == -1)
	{
		socket_perror("init_conn_sock: set_socket_nonblocking 1", 0);
		exit(8);
	}
	/*
	 * listen on socket for connections.
	 */
	if (listen(conn_fd, 128) == -1)
	{
		socket_perror("init_conn_sock: listen", 0);
		exit(10);
	}DBG(("listening for connections on port %d", port_num));
}

/*
 * SIGPIPE handler -- does very little for now.
 */
#ifdef SIGNAL_FUNC_TAKES_INT
void sigpipe_handler(int sig)
{
#else
	void sigpipe_handler()
	{
#endif
	fprintf(stderr, "SIGPIPE received.\n");
}

/*
 * I/O handler.
 */INLINE void aserv_process_io(int nb)
{
	int i;

	switch (nb)
	{
		case -1:
			debug_perror("sigio_handler: select", 0);
			break;
		case 0:
			break;
		default:
			/*
			 * check for new connection.
			 */
			if (FD_ISSET(conn_fd, &readmask))
			{
				DBG(("sigio_handler: NEW_CONN"));
				enqueue_datapending(conn_fd, NEW_CONN);
			}
			/*
			 * check for data pending on established connections.
			 */
			for (i = 0; i < MAX_CONNS; i++)
			{
				if (FD_ISSET(all_conns[i].fd, &readmask))
				{
					DBG(("sigio_handler: CONN"));
					enqueue_datapending(all_conns[i].fd, CONN);
				}
			}
			break;
	}
}

INLINE_STATIC void process_queue()
{
	int i;

	for (i = 0; queue_head && (i < MAX_EVENTS_TO_PROCESS); i++)
	{
		handle_top_event();
		dequeue_top_event();
	}
}

void enqueue_datapending(int fd, int fd_type)
{
	queue_element_ptr new_queue_element;

	pop_queue_element(&new_queue_element);
	new_queue_element->event_type = fd_type;
	new_queue_element->fd = fd;
	new_queue_element->next = NULL;
	if (queue_head)
	{
		queue_tail->next = new_queue_element;
	}
	else
	{
		queue_head = new_queue_element;
	}
	queue_tail = new_queue_element;
}

void dequeue_top_event()
{
	queue_element_ptr top_queue_element;

	if (queue_head)
	{
		top_queue_element = queue_head;
		queue_head = queue_head->next;
		push_queue_element(top_queue_element);
	}
	else
	{
		fprintf(stderr,
		        "dequeue_top_event: tried to dequeue from empty queue!\n");
	}
}

void pop_queue_element(queue_element_ptr * the_queue_element)
{
	if ((*the_queue_element = stack_head))
		stack_head = stack_head->next;
	else
		*the_queue_element = (queue_element_ptr) malloc(sizeof(queue_element));
	queue_length++;
}

void push_queue_element(queue_element_ptr the_queue_element)
{
	the_queue_element->next = stack_head;
	stack_head = the_queue_element;
	queue_length--;
}

void handle_top_event()
{
	switch (queue_head->event_type)
	{
		case NEW_CONN:
			DBG(("handle_top_event: NEW_CONN"));
			new_conn_handler();
			break;
		case CONN:
			DBG(("handle_top_event: CONN data on fd %d", queue_head->fd));
			conn_data_handler(queue_head->fd);
			break;
		default:
			fprintf(stderr, "handle_top_event: unknown event type %d\n",
			        queue_head->event_type);
			break;
	}
}

/*
 * This is the new connection handler. This function is called by the
 * event handler when data is pending on the listening socket (conn_fd).
 * If space is available, an interactive data structure is initialized and
 * the connected is established.
 */
void new_conn_handler()
{
	struct sockaddr_in client;
	int client_len;
	struct hostent *c_hostent;
	int new_fd;
	int conn_index;

	client_len = sizeof(client);
	new_fd = accept(conn_fd, (struct sockaddr *) &client, (int *) &client_len);
	if (new_fd == -1)
	{
		socket_perror("new_conn_handler: accept", 0);
		return;
	}
	if (set_socket_nonblocking(new_fd, 1) == -1)
	{
		socket_perror("new_conn_handler: set_socket_nonblocking 1", 0);
		OS_socket_close(new_fd);
		return;
	}
	if (total_conns >= MAX_CONNS)
	{
		char *message = "no available slots -- closing connection.\n";

		fprintf(stderr, "new_conn_handler: no available connection slots.\n");
		OS_socket_write(new_fd, message, strlen(message));
		if (OS_socket_close(new_fd) == -1)
			socket_perror("new_conn_handler: close", 0);
		return;
	}
	/* get some information about new connection */
	c_hostent = gethostbyaddr((char *) &client.sin_addr.s_addr,
	        sizeof(client.sin_addr.s_addr), AF_INET);
	for (conn_index = 0; conn_index < MAX_CONNS; conn_index++)
	{
		if (all_conns[conn_index].state == CONN_CLOSED)
		{
			DBG(("new_conn_handler: opening conn index %d", conn_index));
			/* update global data for new fd */
			all_conns[conn_index].fd = new_fd;
			all_conns[conn_index].state = CONN_OPEN;
			all_conns[conn_index].addr = client;
			if (c_hostent)
				strcpy(all_conns[conn_index].sname, c_hostent->h_name);
			else
				strcpy(all_conns[conn_index].sname, "<unknown>");
			total_conns++;
			return;
		}
	}
	fprintf(stderr, "new_conn_handler: sanity check failed!\n");
}

void conn_data_handler(int fd)
{
	int conn_index;
	int buf_index;
	int num_bytes;
	int msgtype;
	int leftover;
	char *buf;
	int res, msglen, expecting;
	long unread_bytes;

	if ((conn_index = index_by_fd(fd)) == -1)
	{
		fprintf(stderr, "conn_data_handler: invalid fd.\n");
		return;
	}DBG(("conn_data_handler: read on fd %d", fd));

	/* append new data to end of leftover data (if any) */
	leftover = all_conns[conn_index].leftover;
	buf = (char *) &all_conns[conn_index].buf[0];
	num_bytes = OS_socket_read(fd, buf + leftover, (IN_BUF_SIZE - 1) - leftover);

	switch (num_bytes)
	{
		case -1:
			switch (socket_errno)
			{
				case EWOULDBLOCK:
					DBG(("conn_data_handler: read on fd %d: Operation would block.",
									fd));
					break;
				default:
					socket_perror("conn_data_handler: read", 0);
					terminate(conn_index);
					break;
			}
			break;
		case 0:
			if (all_conns[conn_index].state == CONN_CLOSED)
				fprintf(stderr,
				        "get_user_data: tried to read from closed fd.\n");
			terminate(conn_index);
			break;
		default:
			DBG(("conn_data_handler: read %d bytes on fd %d", num_bytes, fd));
			num_bytes += leftover;
			buf_index = 0;
			expecting = 0;
			while (num_bytes > sizeof(int) && buf_index < (IN_BUF_SIZE - 1))
			{
				/* get message type */
				memcpy((char *) &msgtype, (char *) &buf[buf_index],
				        sizeof(int));
				DBG(("conn_data_handler: message type: %d", msgtype));

				if (msgtype == NAMEBYIP)
				{
					if (buf[buf_index + sizeof(int)] == '\0')
					{
						/* no data here...resync */
						buf_index++;
						num_bytes--;
						continue;
					}
					if (expecting && num_bytes < expecting)
					{
						/*
						 * message truncated...back up to DATALEN message; exit
						 * loop...and try again later
						 */
						buf_index -= (sizeof(int) + sizeof(int));
						num_bytes += (sizeof(int) + sizeof(int));
						break;
					}
					res = name_by_ip(conn_index, &buf[buf_index]);
				}
				else if (msgtype == IPBYNAME)
				{
					if (buf[buf_index + sizeof(int)] == '\0')
					{
						/* no data here...resync */
						buf_index++;
						num_bytes--;
						continue;
					}
					if (expecting && num_bytes < expecting)
					{
						/*
						 * message truncated...back up to DATALEN message; exit
						 * loop...and try again later
						 */
						buf_index -= (sizeof(int) + sizeof(int));
						num_bytes += (sizeof(int) + sizeof(int));
						break;
					}
					res = ip_by_name(conn_index, &buf[buf_index]);
				}
				else if (msgtype == DATALEN)
				{
					if (num_bytes > (sizeof(int) + sizeof(int)))
					{
						memcpy((char *) &expecting,
						        (char *) &buf[buf_index + sizeof(int)],
						        sizeof(int));
						/*
						 * advance to next message
						 */
						buf_index += (sizeof(int) + sizeof(int));
						num_bytes -= (sizeof(int) + sizeof(int));
						if (expecting > IN_BUF_SIZE || expecting <= 0)
						{
							fprintf(stderr,
							        "conn_data_handler: bad data length %d\n",
							        expecting);
							expecting = 0;
						}
						continue;
					}
					else
					{
						/*
						 * not enough bytes...assume truncated; exit loop...we'll
						 * handle this message later
						 */
						break;
					}
				}
				else
				{
					fprintf(stderr,
					        "conn_data_handler: unknown message type %08x\n",
					        msgtype);
					/* advance through buffer */
					buf_index++;
					num_bytes--;
					continue;
				}

				msglen = (int) (sizeof(int)
				        + strlen(&buf[buf_index + sizeof(int)]) + 1);
				if (res)
				{
					/*
					 * ok...advance to next message
					 */
					buf_index += msglen;
					num_bytes -= msglen;
				}
				else if (msglen < num_bytes
				        || (expecting && expecting == msglen))
				{
					/*
					 * failed...
					 */

					/*
					 * this was a complete message...advance to the next one
					 */
					msglen = (int) (sizeof(int)
					        + strlen(&buf[buf_index + sizeof(int)]) + 1);
					buf_index += msglen;
					num_bytes -= msglen;
					expecting = 0;
				}
				else if (!OS_socket_ioctl(fd, FIONREAD, &unread_bytes)
				        && unread_bytes > 0)
				{
					/*
					 * msglen == num_bytes could be a complete message... if
					 * there's unread data we'll assume it was truncated
					 */
					break;
				}
				else
				{
					/*
					 * nothing more?  then discard message (it was the last one)
					 */
					buf_index = 0;
					num_bytes = 0;
					break;
				}
			}

			/* keep track of leftover buffer contents */
			if (num_bytes && buf_index)
				memmove(buf, &buf[buf_index], num_bytes);
			buf[num_bytes] = '\0';
			all_conns[conn_index].leftover = num_bytes;

			break;
	}
}

#define OUT_BUF_SIZE 80

int ip_by_name(int conn_index, char * buf)
{
	struct hostent *hp;
	struct in_addr my_in_addr;
	static char out_buf[OUT_BUF_SIZE];

	hp = gethostbyname(&buf[sizeof(int)]);
	if (hp == NULL)
	{
		/* Failed :( */
		sprintf(out_buf, "%s 0\n", &buf[sizeof(int)]);
		DBG(("%s", out_buf));OS_socket_write(
		        all_conns[conn_index].fd, out_buf, strlen(out_buf));
		return 0;
	}
	else
	{
		/* Success! */
		memcpy(&my_in_addr, hp->h_addr, sizeof(struct in_addr));
		sprintf(out_buf, "%s %s\n", &buf[sizeof(int)], inet_ntoa(my_in_addr));
		DBG(("%s", out_buf));OS_socket_write(
		        all_conns[conn_index].fd, out_buf, strlen(out_buf));
		return 1;
	}
} /* ip_by_name() */

int name_by_ip(int conn_index, char * buf)
{
	long addr;
	struct hostent *hp;
	static char out_buf[OUT_BUF_SIZE];

	if ((addr = inet_addr(&buf[sizeof(int)])) == -1)
	{
		sprintf(out_buf, "%s 0\n", &buf[sizeof(int)]);
		DBG(("name_by_ip: malformed address request."));OS_socket_write(
		        all_conns[conn_index].fd, out_buf, strlen(out_buf));
		return 0;
	}
	if ((hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET)))
	{
		sprintf(out_buf, "%s %s\n", &buf[sizeof(int)], hp->h_name);
		DBG(("%s", out_buf));OS_socket_write(
		        all_conns[conn_index].fd, out_buf, strlen(out_buf));
		return 1;
	}
	else
	{
		sprintf(out_buf, "%s 0\n", &buf[sizeof(int)]);
		DBG(("%s", out_buf));OS_socket_write(
		        all_conns[conn_index].fd, out_buf, strlen(out_buf));
		DBG(("name_by_ip: unable to resolve address."));
		return 0;
	}
}

int index_by_fd(int fd)
{
	int i;

	for (i = 0; i < MAX_CONNS; i++)
	{
		if ((all_conns[i].state == CONN_OPEN) && (all_conns[i].fd == fd))
			return (i);
	}
	return (-1);
}

void terminate(int conn_index)
{
	if (conn_index < 0 || conn_index >= MAX_CONNS)
	{
		fprintf(stderr, "terminate: conn_index %d out of range.\n", conn_index);
		return;
	}
	if (all_conns[conn_index].state == CONN_CLOSED)
	{
		fprintf(stderr, "terminate: connection %d already closed.\n",
		        conn_index);
		return;
	}DBG(("terminating connection %d", conn_index));

	if (OS_socket_close(all_conns[conn_index].fd) == -1)
	{
		socket_perror("terminate: close", 0);
		return;
	}
	all_conns[conn_index].state = CONN_CLOSED;
	total_conns--;
}

int main(int argc, char ** argv)
{
	int addr_server_port;
	struct timeval timeout;
	int i;
	int nb;
	char *ipaddress = 0;

	if (argc > 1)
	{
		if ((addr_server_port = atoi(argv[1])) == 0)
		{
			fprintf(stderr, "addr_server: malformed port number.\n");
			exit(2);
		}
		if (argc > 2)
		{
			if (inet_addr((ipaddress = argv[2])) == INADDR_NONE)
			{
				fprintf(stderr, "addr_server: malformed ip address.\n");
				exit(3);
			}
		}
	}
	else
	{
		fprintf(stderr, "addr_server: first arg must be port number.\n");
		exit(1);
	}
#if defined(LATTICE) && defined(AMITCP)
	init_conns();
#endif
	init_conn_sock(addr_server_port, ipaddress);
	while (1)
	{
		/*
		 * use finite timeout for robustness.
		 */
		timeout.tv_sec = 2;
		timeout.tv_usec = 0;
		/*
		 * clear selectmasks.
		 */
		FD_ZERO(&readmask);
		/*
		 * set new connection accept fd in readmask.
		 */
		FD_SET(conn_fd, &readmask);
		/*
		 * set active fds in readmask.
		 */
		for (i = 0; i < MAX_CONNS; i++)
		{
			if (all_conns[i].state == CONN_OPEN)
				FD_SET(all_conns[i].fd, &readmask);
		}
#ifndef hpux
		nb = select(FD_SETSIZE, &readmask, (fd_set *) 0, (fd_set *) 0,
		        &timeout);
#else
		nb = select(FD_SETSIZE, (int *) &readmask, (int *) 0, (int *) 0, &timeout);
#endif
		if (nb != 0)
			aserv_process_io(nb);
		process_queue();
	}
	/* the following is to shut lint up */
	/*NOTREACHED*/
	return 0; /* never reached */
}

#ifdef WIN32
void debug_message(char * fmt, ...)
{
	static char deb_buf[100];
	static char *deb = deb_buf;
	va_list args;
	V_DCL(char *fmt);

	V_START(args, fmt);
	V_VAR(char *, fmt, args);
	vfprintf(stderr, fmt, args);
	fflush(stderr);
	va_end(args);
}
#endif
